using PathX.Collection;
using UnityEngine;
using System.Collections;
using FPLibrary;

namespace PathX
{
    public abstract class Cell : IPathNode
    {
        private CellMatrix _parent;
        private AttributeMask _blockMask;
        public FPVector position { get; private set; }
        public CellMatrix parent => _parent;
        public int matrixPosX { get; private set; }
        public int matrixPosZ { get; private set; }
        public int cost { get; }
        public bool isPermanentlyBlocked { get; set; }
        
        public Cell(CellMatrix parent, FPVector position, int matrixPosX, int matrixPosZ, bool blocked)
        {
            this.position = position;
            this.matrixPosX = matrixPosX;
            this.matrixPosZ = matrixPosZ;

            _parent = parent;

            isPermanentlyBlocked = blocked;
        }
        public bool IsWalkable(AttributeMask mask)
        {
            if (isPermanentlyBlocked)
                return false;
            if (mask == AttributeMask.None)
                return _blockMask == AttributeMask.None;

            return (_blockMask & mask) != mask;
        }

        public virtual bool IsWalkableWithClearance(IUnitProperties unitProps)
        {
            return IsWalkable(unitProps.attributes);
        }

        public abstract bool IsWalkableFrom(IGridCell neighbour, IUnitProperties unitProps);

        public abstract bool IsWalkableFromWithClearance(IGridCell neighbour, IUnitProperties unitProps);

        public Cell GetNeighbour(VectorXZ offset)
        {
            return GetNeighbour(offset.x, offset.z);
        }

        public Cell GetNeighbour(int dx, int dz)
        {
            var x = this.matrixPosX + dx;
            var z = this.matrixPosZ + dz;

            return parent[x, z];
        }

        public int GetNeighbours(Cell[] neighboursBuffer)
        {
            var cx = this.matrixPosX;
            var cz = this.matrixPosZ;

            int count = 0;
            for (int x = -1; x < 2; x++)
            {
                for (int z = -1; z < 2; z++)
                {
                    if (z == 0 && x == 0)
                    {
                        continue;
                    }

                    var n = parent[cx + x, cz + z];
                    if (n != null)
                    {
                        neighboursBuffer[count++] = n;
                    }
                }
            }

            return count;
        }

        public int g { get; set; }
        public int h { get; set; }
        public int f { get; set; }
        public bool isClosed { get; set; }
        public IPathNode predecessor { get; set; }

        public void GetWalkableNeighbours(DynamicArray<IPathNode> neighbours, IUnitProperties unitProps, bool cornerCuttingAllowed,
            bool preventDiagonalMoves)
        {
            var @this = this as IPathNode;

            //Straight move neighbours
            bool uw = @this.TryGetWalkableNeighbour(0, 1, unitProps, neighbours);

            bool dw = @this.TryGetWalkableNeighbour(0, -1, unitProps, neighbours);

            bool rw = @this.TryGetWalkableNeighbour(1, 0, unitProps, neighbours);

            bool lw = @this.TryGetWalkableNeighbour(-1, 0, unitProps, neighbours);

            if (preventDiagonalMoves)
            {
                return;
            }

            //Diagonal neighbours. First determine if they are unwalkable as a consequence of their straight neighbours
            bool urw, drw, dlw, ulw;
            if (cornerCuttingAllowed)
            {
                urw = uw || rw;
                drw = dw || rw;
                dlw = dw || lw;
                ulw = uw || lw;
            }
            else
            {
                urw = uw && rw;
                drw = dw && rw;
                dlw = dw && lw;
                ulw = uw && lw;
            }

            if (urw)
            {
                @this.TryGetWalkableNeighbour(1, 1, unitProps, neighbours);
            }

            if (drw)
            {
                @this.TryGetWalkableNeighbour(1, -1, unitProps, neighbours);
            }

            if (dlw)
            {
                @this.TryGetWalkableNeighbour(-1, -1, unitProps, neighbours);
            }

            if (ulw)
            {
                @this.TryGetWalkableNeighbour(-1, 1, unitProps, neighbours);
            }
        }

        public bool TryGetWalkableNeighbour(int dx, int dz, IUnitProperties unitProps, DynamicArray<IPathNode> neighbours)
        {
            var x = this.matrixPosX + dx;
            var z = this.matrixPosZ + dz;
            
            var neighbour = _parent[x, z];

            if (neighbour == null)
            {
                return false;
            }

            if (neighbour.IsWalkableFromWithClearance(this, unitProps))
            {
                neighbours.Add(neighbour);
                return true;
            }

            return false;
        }
        
        internal void UpdateState(bool isBlocked)
        {
            var pos = this.position;
            isPermanentlyBlocked = isBlocked;
        }
    }

    public sealed class FlatCell : Cell
    {
        public FlatCell(CellMatrix parent, FPVector position, int matrixPosX, int matrixPosZ, bool blocked)
            : base(parent, position, matrixPosX, matrixPosZ, blocked)
        {
        }
        
        public override bool IsWalkableFrom(IGridCell neighbour, IUnitProperties unitProps)
        {
            return IsWalkable(unitProps.attributes);
        }

        public override bool IsWalkableFromWithClearance(IGridCell neighbour, IUnitProperties unitProps)
        {
            return IsWalkableWithClearance(unitProps);
        }
    }
}